import os, pygame, json, types, traceback, sys
from pygame.locals import *

STDOUT_LINE = "-" * 78
SPRITESHEET_CACHE = {}

class Spritesheet:
  def __init__(self, filename, sheet_type="map"):
    if filename in SPRITESHEET_CACHE.keys():
      self.sheet = SPRITESHEET_CACHE[filename]["sheet"]
      self.sheet_type = SPRITESHEET_CACHE[filename]["sheet_type"]
    else:
      self.sheet = pygame.image.load(filename).convert()
      self.sheet_type = sheet_type
      SPRITESHEET_CACHE[filename] = {
        "sheet": self.sheet,
        "sheet_type": self.sheet_type
      }
  def imgat(self, rect, colorkey = None):
    "get a single sprite from the spritesheet"
    rect = Rect(rect)
    image = pygame.Surface(rect.size).convert()
    image.blit(self.sheet, (0, 0), rect)
    if colorkey is not None:
      if colorkey is -1:
        colorkey = image.get_at((0, 0))
      image.set_colorkey(colorkey, RLEACCEL)
    return image
  def imgsat(self, rects, colorkey = None):
    "get a list of rects from the spritesheet"
    imgs = []
    for rect in rects:
      imgs.append(self.imgat(rect, colorkey))
    return imgs
  def parse(self, size, colorkey=None):
    "returns a list of all sprites of size by size from self.sheet"
    width, height = self.sheet.get_size()
    if self.sheet_type == "map":
      imgs = []
      for row in xrange(0,height,size):
        for col in xrange(0,width,size):
          imgs.append(self.imgat(Rect(col,row,size,size), colorkey))
      return imgs
    elif self.sheet_type == "characters": 
      dirs = ["north","east","south","west"]
      # each sheet of this type has 8 images that go together, 
      # they are in 2x4 formations
      # N1, N2
      # E1, E2
      # S1, S2
      # W1, W2
      imgs = []
      # there are 8 sprite images for a character, 
      # 2 animation frames by 4 directions
      for x in xrange(0,(height*width/(size*size*8))):
        imgs.append({
          "north":[None,None],
          "east" :[None,None],
          "south":[None,None],
          "west" :[None,None]
        })
      for row in xrange(0,height,size):
        for col in xrange(0,width,size):
          _col = col/size
          _row = row/size
          ch_num = (_col/2) + (_row/4) * width/(size*2)
          direction = _row % 4
          anim = _col % 2
          imgs[ch_num][dirs[direction]][anim] = self.imgat(
                                          Rect(col,row,size,size), colorkey)
      return imgs

class TileSprite(pygame.sprite.Sprite):
  "A single tile"
  def __init__(self,index, rect):
    pygame.sprite.Sprite.__init__(self) #call Sprite intializer
    self.rect = Rect(rect)
    self.index = index
  
  def update(self):
    pass

class Layer:
  height   = 0      # number of tiles tall
  width    = 0      # number of tiles wide
  name     = ""     # name of the layer
  size     = (0,0)  # width x height in pixels
  data     = []     # tile data
  sheet    = ""     # filename of the spritesheet
  tilesize = 0      # height and/or width of each tile
  colorkey = None   # colorkey for transparency
  tiles    = []     # list of tiles

  def __init__(self, layer, tileset, directory, group):
    self.tilesize    = int(tileset["tilesize"])
    self.sheet       = os.path.join( directory, "%s" % tileset["name"] )
    self.height      = int(layer["height"])
    self.width       = int(layer["width"])
    self.size        = (self.width * self.tilesize, 
                        self.height * self.tilesize)
    self.name        = layer["name"]
    self.data        = self.parseData(layer["data"])
    self.colorkey    = tileset["colorkey"]
    self.spritesheet = Spritesheet(self.sheet)
    self.tiles       = self.spritesheet.parse(self.tilesize,self.colorkey)
    self.surface     = pygame.Surface(self.size).convert()
    self.draw(group)
        
  def drawTile(self, tile, x, y):
    "draw a single tile"
    self.surface.blit(tile, (self.tilesize*x,self.tilesize*y) )
  
  def parseData(self,data):
    """
    Converts the map data into a 2d array of integers that correspond with 
    the spritesheet indices. The map editor 'Tiled' starts the indices at 1
    instead of 0. 
    
    Goes from: 
    
    [{
      "gid": 1
    }, ... 
    {
      "gid": 5
    }
    ]
    
    to: 
    
    [0 ... 5]
    """
    tiles = data["tile"]
    d = [int(tiles[i]["gid"])-1 for i in xrange(0,len(tiles))]
    d = [d[i:i+self.width] for i in xrange(0,len(d),self.width)]
    return d
  
  def draw(self, group):
    for row in xrange(0,self.height):
      for col in xrange(0,self.width):
        tile_index = self.data[row][col]
        self.drawTile(self.tiles[tile_index], col, row )
        group.add( 
          TileSprite( tile_index, 
            (self.tilesize*col,self.tilesize*row,self.tilesize,self.tilesize)  
          )
        )


class Tyler:
  """Reads a directory of sprite sheets (.bmp) and builds tilesets"""
  layers       = {}
  spritegroups = {}
  spritesheets = {}
  blitorder    = []
  mapstructure = {}
  
  def __init__(self,mapfile, directory='spritesheets' ):
    # look for the index file
    fp = open(os.path.join(directory,'sheet.index'))
    self.spritesheets = json.read(fp.read())
    fp.close()
    fp = open( os.path.join('maps', "%s.js" % mapfile) )
    self.mapstructure = json.read(fp.read())["map"]
    fp.close()
  
  def createLayer(self, layer, tileset, directory='spritesheets' ):
    try:
      t = self.spritesheets[tileset]
      n = layer["name"]
      self.spritegroups[n] = pygame.sprite.Group()
      self.layers[n] = Layer( layer, t, directory, self.spritegroups[n] )
      return self.layers[n]
    except:
      print STDOUT_LINE
      print traceback.print_exc(file=sys.stdout)
      print STDOUT_LINE
  
  def draw(self):
    l = self.mapstructure["layer"]
    layers = type(l) == types.DictType and [l] or l
    
    tileset = self.mapstructure["tileset"]["name"].lower()
    
    for layer in layers:
      self.blitorder.append( self.createLayer(layer,tileset) )

  def getLayers(self):
    return self.blitorder  
  
